#  File src/library/grid/R/grab.R
#  Part of the R package, http://www.R-project.org
#
#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation; either version 2 of the License, or
#  (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  A copy of the GNU General Public License is available at
#  http://www.r-project.org/Licenses/

#########
# Generate a gTree from the current display list
#
# Or from an expression
# (recording on to a null graphics device)
#########
rootVP <- function(pvp) {
  match(pvp$name, "ROOT", nomatch=FALSE)
}

# List the children of the current vp (as a vpList)
current.vpList <- function() {
  cpvp <- grid.Call("L_currentViewport")
  if (length(ls(cpvp$children, all.names=TRUE)) == 0)
    NULL
  else
    vpListFromNode(cpvp)
}

current.vpNames <-function() {
  ls(grid.Call("L_currentViewport")$children)
}

# vp might be a viewport, or a vpList, or a vpStack, or a vpTree
vpExists <- function(vp) {
  UseMethod("vpExists")
}

vpExists.viewport <- function(vp) {
  vp$name %in% ls(.Call(L_currentViewport)$children)
}

vpExists.vpStack <- function(vp) {
  vpExists(vp[[1L]])
}

vpExists.vpList <- function(vp) {
  any(sapply(vp, vpExists, simplify=TRUE))
}

vpExists.vpTree <- function(vp) {
  vpExists(vp$parent)
}

wrap <- function(x) {
  UseMethod("wrap")
}

wrap.default <- function(x) {
  if (!is.null(x))
    stop("Invalid display list element")
  NULL
}

wrap.grob <- function(x) {
  x
}

wrap.viewport <- function(x) {
  recordGrob(pushViewport(vp), list(vp=x))
}

wrap.pop <- function(x) {
  recordGrob(popViewport(n), list(n=x))
}

wrap.up <- function(x) {
  recordGrob(upViewport(n), list(n=x))
}

wrap.down <- function(x) {
  recordGrob(downViewport(path), list(path=x))
}

# Grab the display list on the current device
# ... are passed to gTree
# If warn is 0, issue no warnings
# If warn is 1, issue warnings about situations that are definitely
#   NOT captured correctly (e.g., reuse of top-level grob name)
# If warn is 2, issue warnings about situations that
#   MAY not get captured correctly (e.g., top-level downViewport)
# If wrap is TRUE, grab will wrap all pushes and grobs
#   in a gTree
grabDL <- function(warn, wrap, ...) {
  gList <- NULL
  dl.index <- grid.Call("L_getDLindex")
  if (dl.index > 1) {
    if (warn > 0) {
      names <- getNames()
      # Check for overwriting existing grob
      if (length(unique(names)) != length(names))
        warning("grob(s) overwritten (grab WILL not be faithful; try 'wrap=TRUE')")
    }
    grid.newpage(recording=FALSE)
    # Start at 2 because first element is viewport[ROOT]
    for (i in 2:dl.index) {
      # Do all of this as a big ifelse rather than
      # dispatching to a function call per element because
      # we need to work with whole DL at times, not
      # just individual elements
      elt <- grid.Call("L_getDLelt", as.integer(i - 1))
      if (wrap)
        gList <- addToGList(wrap(elt), gList)
      else {

        ###########
        # grabGrob
        ###########
        if (inherits(elt, "grob")) {
          # Enforce grob$vp now and set grob$vp to NULL
          # Will be replaced later with full vpPath
          tempvp <- elt$vp
          if (warn > 1) {
            # Check to see if about to push a viewport
            # with existing viewport name
            if (inherits(tempvp, "viewport") &&
                vpExists(tempvp))
              warning("viewport overwritten (grab MAY not be faithful)")
          }
          if (!is.null(tempvp))
            tempdepth <- depth(tempvp)
          grid.draw(tempvp, recording=FALSE)
          # vpPath after grob$vp slot has been pushed
          # Has to be recorded here in case grob drawing
          # pushes (and does not pop) more viewports
          drawPath <- current.vpPath()
          elt$vp <- NULL
          grid.draw(elt, recording=FALSE)
          if (warn > 1) {
            # Compare new vpPath
            # If not same, the grob has pushed some viewports
            # and not popped or upped them
            pathSame <- TRUE
            if (!(is.null(drawPath) && is.null(current.vpPath()))) {
              if (is.null(drawPath))
                pathSame = FALSE
              else if (is.null(current.vpPath()))
                pathSame = FALSE
              else if (as.character(drawPath) !=
                       as.character(current.vpPath()))
                pathSame = FALSE
            }
            if (!pathSame)
              warning("grob pushed viewports and did not pop/up them (grab MAY not be faithful)")
          }
          elt$vp <- drawPath
          if (!is.null(tempvp))
            upViewport(tempdepth, recording=FALSE)
          gList <- addToGList(elt, gList)
        ###########
        # grabViewport
        ###########
        } else if (inherits(elt, "viewport")) {
          # Includes viewports, vpLists, vpTrees, and vpStacks
          # Check to see if about to push a viewport
          # with existing viewport name
          if (vpExists(elt))
            warning("viewport overwritten (grab MAY not be faithful)")
          grid.draw(elt, recording=FALSE)
        ###########
        # grabPop
        ###########
        } else if (inherits(elt, "pop")) {
          # Replace pop with up
          upViewport(elt, recording=FALSE)

        ###########
        # grabDefault
        ###########
        } else {
          grid.draw(elt, recording=FALSE)
        }
      } # matches if (wrap)
    }
    # Go to top level
    upViewport(0, recording=FALSE)
    gTree(children=gList, childrenvp=current.vpList(), ...)
  } else {
    NULL
  }
}

# expr is ignored if dev is NULL
# otherwise, it should be an expression, like postscript("myfile.ps")
grid.grab <- function(warn=2, wrap=FALSE, ...) {
  grabDL(warn, wrap, ...)
}

grid.grabExpr <- function(expr, warn=2, wrap=FALSE, ...) {
  # Start a new null device
  .Call("R_GD_nullDevice")
  # If something goes wrong, want to revert to the current device
  on.exit(dev.off())
  # Run the graphics code in expr
  # Rely on lazy evaluation for correct "timing"
  eval(expr)
  # Grab the DL on the new device
  grabDL(warn, wrap, ...)
}
